﻿using System;
using BrightSword.LightSaber;
using BrightSword.LightSaber.Module;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Test.LightSaber
{
    /// <summary>
    ///This is a test class for EvaluatorTest and is intended
    ///to contain all EvaluatorTest Unit Tests
    ///</summary>
    [TestClass]
    public class EvaluatorTest
    {
        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext { get; set; }

        /// <summary>
        ///A test for ParseAndEvaluate
        ///</summary>
        [TestMethod]
        public void ParseAndEvaluateTest()
        {
            Library.Load(typeof (LispCommon));
            Library.Load(typeof (LispDate));
            Library.Load(typeof (LispNumber));
            Library.Load(typeof (LispBoolean));
            Library.Load(typeof (LispCompare));
            Library.Load(typeof (LispCore));

            var callStack = new CallStack();
            callStack["height"] = 1.8;
            callStack["weight"] = 78;

            callStack["g"] = "female";
            callStack["hbp"] = 128;
            callStack["bt"] = 94;
            callStack["bmi"] = 24.1;

            Assert.AreEqual("(+ 1 1 1 1)".Parse().Eval(callStack).Value, 4.0);
            Assert.AreEqual("(+ 'Hello' \" World\")".Parse().Eval(callStack).Value, "'Hello World'");
            Assert.AreEqual("(== 1 1)".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(== (+ 1 1 1 1) (+ 3 1))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(== (+ 1 1 1 1) (+ 3 1) (* 2 2) (+ 2 2) (/ 16 4))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual(
                "(~ (+ (/ 3.14159265358979 4) (/ 3.14159265358979 4) (/ 3.14159265358979 4) (/ 3.14159265358979 4)))".Parse().Eval(callStack).Value,
                -3.14159265358979);
            Assert.AreEqual("(&& (== g 'female') (&& (> hbp 120) (< bt 96) (>= bmi 24.0)))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(> 4 3 2 1)".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(== (+ 1 1 1 1) (+ 3 1) (* 2 2) (+ 2 2) (/ 16 4))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(<= (+ 1 1 1 1) (+ 3 1.1) (* 2 2.1) (+ 2 3) (/ 16 3))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(t)".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(or t true (true))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(and t true (true))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(or f false (false))".Parse().Eval(callStack).Value, false);
            Assert.AreEqual("(and f false (false))".Parse().Eval(callStack).Value, false);
            Assert.AreEqual("(or t nil)".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(and t (nil))".Parse().Eval(callStack).Value, false);
            Assert.AreEqual("(unique 1 'false' true)".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(descending 4 2 3 1)".Parse().Eval(callStack).Value, false);
            Assert.AreEqual("(< 4 3 2 1)".Parse().Eval(callStack).Value, false);
            Assert.AreEqual("(!= (+ 1 1 1 1) (+ 3 1) (* 2 2) (+ 2 2) (/ 16 4))".Parse().Eval(callStack).Value, false);
            Assert.AreEqual("(> (+ 1 1 1 1) (+ 3 1.1) (/ 16 3))".Parse().Eval(callStack).Value, false);
            Assert.AreEqual("(|| _unknown_symbol true)".Parse().Eval(callStack).Value.ToString(), "[_unknown_symbol]");
            Assert.AreEqual("(&& _unknown_symbol true)".Parse().Eval(callStack).Value.ToString(), "[_unknown_symbol]");
            Assert.AreEqual(
                "(|| (== _unknown_symbol 'female') (&& (> hbp 120) (< bt 96) (>= bmi 24.0)))".Parse().Eval(callStack).ToString(), "[_unknown_symbol]");
            Assert.AreEqual(
                "(|| (== _unknown_symbol 'female') (&& (> hbp 120) (< _yet_another_unknown_symbol 96) (>= bmi 24.0)))".Parse().Eval(callStack).ToString(
                    ),
                "[_unknown_symbol _yet_another_unknown_symbol]");
            Assert.AreEqual("(unique 1 'test' 10 10)".Parse().Eval(callStack).Value, false);
            Assert.AreEqual("(int-p 12)".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(int-p 3.14159265358979)".Parse().Eval(callStack).Value, false);
            Assert.AreEqual("(setq w 25)".Parse().Eval(callStack).Value, 25.0);
            Assert.AreEqual("(int-p w)".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(defun add (x) (== x 'Hello World'))".Parse().Eval(callStack).Value, "add");
            Assert.AreEqual("(add 'Hello World' 'ignore')".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(add 'nonsense' 'meep')".Parse().Eval(callStack).Value, false);
            Assert.AreEqual("(defun square (x) (* x x))".Parse().Eval(callStack).Value, "square");
            Assert.AreEqual("(square 4)".Parse().Eval(callStack).Value, 16.0);
            Assert.AreEqual("(square (square 2))".Parse().Eval(callStack).Value, 16.0);
            Assert.AreEqual("(defun double (x) (* 2 x))".Parse().Eval(callStack).Value, "double");
            Assert.AreEqual("(square (double 4))".Parse().Eval(callStack).Value, 64.0);
            Assert.AreEqual("(double (square 4))".Parse().Eval(callStack).Value, 32.0);
            Assert.AreEqual("(defun add_num (x y) (+ x y))".Parse().Eval(callStack).Value, "add_num");
            Assert.AreEqual("(add_num (square 20) (square 20))".Parse().Eval(callStack).Value, 800.0);
            Assert.AreEqual("((cat 's' 'qua' 're') 4)".Parse().Eval(callStack).Value, 16.0);
            Assert.AreEqual("(setq v (+ 2 3))".Parse().Eval(callStack).Value, 5.0);
            Assert.AreEqual("(setq w 25)".Parse().Eval(callStack).Value, 25.0);
            Assert.AreEqual("(setq z (+ v w))".Parse().Eval(callStack).Value, 30.0);
            Assert.AreEqual("v".Parse().Eval(callStack).Value, 5.0);
        }

        [TestMethod]
        public void BasicLispTest()
        {
            Library.Load(typeof (LispCommon));
            Library.Load(typeof (LispDate));
            Library.Load(typeof (LispNumber));
            Library.Load(typeof (LispBoolean));
            Library.Load(typeof (LispCompare));
            Library.Load(typeof (LispCore));

            var callStack = new CallStack();

            @"(defun make-list (x) 
(cond
    ((list? x) x)
    (t (cons x nil))))".Parse().Eval(callStack);

            @"(defun append (l x) 
    (cond
        ((atom? l) (cons l (make-list x)))
        ((nil? l) (make-list x))
        (t (cons (car l) (append (cdr l) x)))
    ))"
                .Parse().Eval(callStack);

            @"(defun reverse (l)
    (cond
        ((nil? l) l)
        ((atom? l) nil)
        ((list? (car l)) (append (reverse (cdr l)) (list (reverse (car l)))))
	    (t 
            (append (reverse (cdr l)) (car l))
        )
    ))"
                .Parse().Eval(callStack);

            Assert.AreEqual("(cons 'a' nil)".Parse().Eval(callStack).Value.ToString(), "(a)");
            Assert.AreEqual("(cons 'a' 'b')".Parse().Eval(callStack).Value.ToString(), "(a . b)");
            Assert.AreEqual("(setq l (cons 'a' 'b'))".Parse().Eval(callStack).Value.ToString(), "(a . b)");
            Assert.AreEqual("(car l)".Parse().Eval(callStack).Value.ToString(), "a");
            Assert.AreEqual("(cdr l)".Parse().Eval(callStack).Value.ToString(), "b");
            Assert.AreEqual("(car (cdr l))".Parse().Eval(callStack).Value.ToString(), "nil");
            Assert.AreEqual("(setq w 25)".Parse().Eval(callStack).Value, 25.0);
            Assert.AreEqual("(cons (+ 1 2 3 4) 'w')".Parse().Eval(callStack).Value.ToString(), "(10 . w)");
            Assert.AreEqual("(cons (+ 1 2 3 4) w)".Parse().Eval(callStack).Value.ToString(), "(10 . 25)");
            Assert.AreEqual("(atom-p w)".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(list-p w)".Parse().Eval(callStack).Value, false);
            Assert.AreEqual("(list-p (cons (+ 1 2 3 4) w))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(cons 'maple' (list 'birch' 'oak' 'larch'))".Parse().Eval(callStack).Value.ToString(), "(maple birch oak larch)");
            Assert.AreEqual("(setq trees (list 'maple' 'birch' 'oak' 'larch'))".Parse().Eval(callStack).Value.ToString(), "(maple birch oak larch)");
            Assert.AreEqual("(list 'maple' (list 'birch' 'oak' 'larch'))".Parse().Eval(callStack).Value.ToString(), "(maple (birch oak larch))");
            Assert.AreEqual("(car trees)".Parse().Eval(callStack).Value.ToString(), "maple");
            Assert.AreEqual("(cdr trees)".Parse().Eval(callStack).Value.ToString(), "(birch oak larch)");
            Assert.AreEqual("(cdr (car trees))".Parse().Eval(callStack).Value.ToString(), "nil");
            Assert.AreEqual("(car (cdr trees))".Parse().Eval(callStack).Value.ToString(), "birch");
            Assert.AreEqual("(atom-p (car trees))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(atom-p (cdr trees))".Parse().Eval(callStack).Value, false);
            Assert.AreEqual("(list-p (car trees))".Parse().Eval(callStack).Value, false);
            Assert.AreEqual("(list-p (cdr trees))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual(
                "(setq inner (cons (list 'inner child' 'inner sibling') 'outer sibling'))".Parse().Eval(callStack).Value.ToString(),
                "(('inner child' 'inner sibling') . 'outer sibling')");
            Assert.AreEqual("(car inner)".Parse().Eval(callStack).Value.ToString(), "('inner child' 'inner sibling')");
            Assert.AreEqual("(list-p (car inner))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(car (car inner))".Parse().Eval(callStack).Value.ToString(), "'inner child'");
            Assert.AreEqual("(atom-p (car (car inner)))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(cdr (car inner))".Parse().Eval(callStack).Value.ToString(), "('inner sibling')");
            Assert.AreEqual("(list-p (cdr (car inner)))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(car (cdr (car inner)))".Parse().Eval(callStack).Value.ToString(), "'inner sibling'");
            Assert.AreEqual("(atom-p (car (cdr (car inner))))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(car (cdr inner))".Parse().Eval(callStack).Value.ToString(), "nil");
            Assert.AreEqual("(cons 'a' (list 'b' 'c' 'd' 'e' 'f'))".Parse().Eval(callStack).Value.ToString(), "(a b c d e f)");
            Assert.AreEqual("(cons (list 'b' 'c' 'd' 'e' 'f') 'a')".Parse().Eval(callStack).Value.ToString(), "((b c d e f) . a)");
            Assert.AreEqual("(cons (list 'a' 'b' 'c') (list 'd' 'e' 'f'))".Parse().Eval(callStack).Value.ToString(), "((a b c) d e f)");
            Assert.AreEqual("(cons (list 'a' 'b' 'c') (list))".Parse().Eval(callStack).Value.ToString(), "((a b c))");
            Assert.AreEqual("(cons (list 'a' 'b' 'c') (list (list 'd' 'e' 'f')))".Parse().Eval(callStack).Value.ToString(), "((a b c) (d e f))");
            Assert.AreEqual("(make-list (list 'a' 'b' 'c'))".Parse().Eval(callStack).Value.ToString(), "(a b c)");
            Assert.AreEqual("(make-list 'a')".Parse().Eval(callStack).Value.ToString(), "(a)");
            Assert.AreEqual("(append (list 'a' 'b' 'c') (list 'd' 'e' 'f') )".Parse().Eval(callStack).Value.ToString(), "(a b c d e f)");
            Assert.AreEqual("(list 'a' 'b' (list 'c' 'd') 'e' 'f')".Parse().Eval(callStack).Value.ToString(), "(a b (c d) e f)");
            Assert.AreEqual("(append (list 'a' 'b' (list 'c' 'd')) (list 'e' 'f'))".Parse().Eval(callStack).Value.ToString(), "(a b (c d) e f)");
            Assert.AreEqual("(append (list 'h' 'i') (list 'g' 'h'))".Parse().Eval(callStack).Value.ToString(), "(h i g h)");
            Assert.AreEqual("(append 'j' (list 'o' 'k' 'e'))".Parse().Eval(callStack).Value.ToString(), "(j o k e)");
            Assert.AreEqual("(cons 'f' nil)".Parse().Eval(callStack).Value.ToString(), "(f)");
            Assert.AreEqual("(cons 'f' (list))".Parse().Eval(callStack).Value.ToString(), "(f)");
            Assert.AreEqual("(nil? (list))".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(nil? ())".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(nil? nil)".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(== () (list) nil)".Parse().Eval(callStack).Value, true);
            Assert.AreEqual("(reverse (list 'a' 'b' 'c'))".Parse().Eval(callStack).Value.ToString(), "(c b a)");
            Assert.AreEqual("(reverse (list 'a' 'b' (list 'c' 'd') 'e' 'f'))".Parse().Eval(callStack).Value.ToString(), "(f e (d c) b a)");
        }

        [TestMethod]
        public void DateTest()
        {
            Library.Load(typeof (LispCommon));
            Library.Load(typeof (LispDate));
            Library.Load(typeof (LispNumber));
            Library.Load(typeof (LispBoolean));
            Library.Load(typeof (LispCompare));
            Library.Load(typeof (LispCore));

            var callStack = new CallStack();

            "(defun yesterday (f?) (date (- (today) 1) f))".Parse().Eval(callStack);
            "(defun tomorrow (f?) (date (+ (today) 1) f))".Parse().Eval(callStack);
            "(defun day_name (x) (date x 'dddd'))".Parse().Eval(callStack);
            "(defun day_of_month (x) (date x 'd'))".Parse().Eval(callStack);
            "(defun month (x) (date x 'M'))".Parse().Eval(callStack);
            "(defun short_month (x) (date x 'MMM'))".Parse().Eval(callStack);
            "(defun long_month (x) (date x 'MMMM'))".Parse().Eval(callStack);
            "(defun short_year (x) (date x 'YY'))".Parse().Eval(callStack);
            "(defun long_year (x) (date x 'YYYY'))".Parse().Eval(callStack);
            "(defun days_to_sunday (d) (- 7 (day_of_week d)))".Parse().Eval(callStack);
            @"(defun next_sunday (x? f?) 
(prog
    (setq d (if (nil? x) today x))
    (date (+ d (days_to_sunday d)) f)))".Parse().Eval(callStack);

            @"(defun next_monday (x? f?) 
(prog
    (setq d (if (nil? x) today x))
    (date (+ d 1 (days_to_sunday d)) f)))".Parse().Eval(callStack);
            @"(defun next_tuesday (x? f?) 
(prog
    (setq d (if (nil? x) today x))
    (date (+ d 2 (days_to_sunday d)) f)))".Parse().Eval(
                callStack);
            @"(defun next_wednesday (x? f?) 
(prog
    (setq d (if (nil? x) today x))
    (date (+ d 3 (days_to_sunday d)) f)))".Parse().Eval(
                callStack);
            @"(defun next_thursday (x? f?) 
(prog
    (setq d (if (nil? x) today x))
    (date (+ d 4 (days_to_sunday d)) f)))".Parse().Eval(
                callStack);
            @"(defun next_friday (x? f?) 
(prog
    (setq d (if (nil? x) today x))
    (date (+ d 5 (days_to_sunday d)) f)))".Parse().Eval(callStack);
            @"(defun next_saturday (x? f?) 
(prog
    (setq d (if (nil? x) today x))
    (date (+ d 6 (days_to_sunday d)) f)))".Parse().Eval(
                callStack);

            Assert.AreEqual("(yesterday)".Parse().Eval(callStack).Value, DateTime.Today.AddDays(-1));
            Assert.AreEqual("(tomorrow)".Parse().Eval(callStack).Value, DateTime.Today.AddDays(1));
            Console.WriteLine("(next_saturday)".Parse().Eval(callStack).Value);
        }
    }
}